using System;
using System.Collections.Generic;
using System.Text;
using NCommet.Core.Agents;
using NCommet.Core.Configuration;
using System.ComponentModel;

namespace NCommet.Core.Services
{
    /// <summary>
    /// Event arguments for simple events that only contain reference to the item involved.
    /// </summary>
    public class ItemEventArgs : EventArgs
    {
        private Item item;

        /// <summary>
        /// Creates a new <see cref="NCommet.Core.Services.ItemEventArgs"/> object about the given <paramref name="item"/>.
        /// </summary>
        /// <param name="item">The item of the event.</param>
        public ItemEventArgs(Item item)
        {
            this.item = item;
        }

        /// <summary>
        /// Returns the item related to this event.
        /// </summary>
        public Item Item { get { return item; } }
    }

    /// <summary>
    /// Event arguments regarding an item event that can be cancelled.
    /// </summary>
    public class ItemCancellableEventArgs : ItemEventArgs
    {
        /// <summary>
        /// Creates a new <c>ItemCancellableEventArgs</c> object about the given <paramref name="item"/>.
        /// </summary>
        /// <param name="item">The item of the event.</param>
        public ItemCancellableEventArgs(Item item) : base(item) { }

        private bool cancel = false;

        /// <summary>
        /// If set <c>true</c> inside an event handler, the operation that triggered the event will be cancelled.
        /// </summary>
        public bool Cancel { get { return cancel; } set { cancel = value; } }
    }

    /// <summary>
    /// Event arguments that are used after an item has changed parent.
    /// </summary>
    public class ItemChangedParentArgs : ItemEventArgs
    {
        /// <summary>
        /// Creates a new <c>ItemChangedParentArgs</c> object.
        /// </summary>
        /// <param name="item">The item that changed parent.</param>
        /// <param name="oldParent">The old parent of <paramref name="item"/>.</param>
        public ItemChangedParentArgs(Item item, Item oldParent) : base(item) { this.oldParent = oldParent; }

        private Item oldParent;

        /// <summary>
        /// Returns the old parent of the item related to the event.
        /// </summary>
        public Item OldParent { get { return oldParent; } }
    }

    /// <summary>
    /// Event arguments that are used before an item changes parent. This event can be cancelled by an event handler.
    /// </summary>
    public class ItemChangingParentEventArgs : ItemCancellableEventArgs
    {
        /// <summary>
        /// Creates a new <c>ItemChangingParentEventArgs</c> object.
        /// </summary>
        /// <param name="item">The item that will change parent.</param>
        /// <param name="newParent">The new candidate parent of <paramref name="item"/>.</param>
        public ItemChangingParentEventArgs(Item item, Item newParent) : base(item) { this.newParent = newParent; }

        private Item newParent;

        /// <summary>
        /// The new candidate parent of the item related to the event.
        /// </summary>
        public Item NewParent { get { return newParent; } }

    }

    /// <summary>
    /// Represents the method that will handle a simple event related to an item.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="e">The arguments of the event.</param>
    public delegate void ItemHandler(object sender, ItemEventArgs e);

    /// <summary>
    /// Represents the method that will handle a cancellable event related to an item.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="e">The arguments of the event.</param>
    public delegate void ItemCancellableHandler(object sender, ItemCancellableEventArgs e);

    /// <summary>
    /// Represents the method that will handle the event triggered after an item's parent has changed.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="e">The arguments of the event.</param>
    public delegate void ItemChangedParentHandler(object sender, ItemChangedParentArgs e);

    /// <summary>
    /// Represents the method that will handle the event triggered before an item's parent changes.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="e">The arguments of the event.</param>
    public delegate void ItemChangingParentHandler(object sender, ItemChangingParentEventArgs e);

    /// <summary>
    /// EventSink triggers events from actions that occur inside items.
    /// When an object wants to receive those item events, it can add event handlers to the events
    /// exposed by EventSink.
    /// </summary>
    /// <remarks>
    /// EventSink provides a central point of event subscription for every item.
    /// </remarks>
    public sealed class EventSink : IDaoEventsListener
    {
        /// <summary>
        /// Occurs when a new transient item is created.
        /// </summary>
        /// <remarks>Fired from <see cref="NCommet.Core.Item.Create(string,string,bool)"/></remarks>
        public event ItemHandler ItemCreated;

        /// <summary>
        /// Occurs when an item is added as a child of another item.
        /// The event args contain the child item.
        /// </summary>
        /// <remarks>
        /// Fired from 
        /// <ul>
        /// <li><see cref="NCommet.Core.ChildrenCollection.Insert" /></li>
        /// <li><see cref="NCommet.Core.ChildrenCollection.Add" /></li>
        /// </ul>
        /// </remarks>
        public event ItemHandler ItemAdded;

        /// <summary>
        /// Occurs before an item is saved.
        /// </summary>
        public event ItemHandler ItemSaving;

        /// <summary>
        /// Occurs after an item is saved.
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <seealso cref="NCommet.Core.Item.Save"/>
        public event ItemHandler ItemSaved;

        /// <summary>
        /// Occurs after an item has changed parent.
        /// The event args contain the item that changed parent.
        /// </summary>
        public event ItemChangedParentHandler ItemChangedParent;

        /// <summary>
        /// Occurs before an item is deleted.
        /// </summary>
        public event ItemCancellableHandler ItemDeleting;

        /// <summary>
        /// Occurs after an item is deleted.
        /// </summary>
        public event ItemHandler ItemDeleted;

        /// <summary>
        /// Occurs when the children collection of an item is cleared.
        /// </summary>
        public event ItemHandler ChildrenCleared;

        /// <summary>
        /// Occurs when the sort order of a child item changes inside its parent's children collection.
        /// The event args contain the parent item.
        /// </summary>
        public event ItemHandler ChildrenReordered;

        /// <summary>
        /// Occurs when an item will change parent.
        /// The event args contain the item that will change parent.
        /// </summary>
        public event ItemChangingParentHandler ItemChangingParent;

        /// <summary>
        /// Occurs when an item is shallow cloned.
        /// The event args contain the new transient clone item.
        /// </summary>
        /// <seealso cref="NCommet.Core.Item.ShallowClone"/>
        public event ItemHandler ItemShallowCloned;

        /// <summary>
        /// Occurs when an item is deep cloned.
        /// The event args contain the new transient clone item.
        /// </summary>
        /// <seealso cref="NCommet.Core.Item.DeepClone(bool)"/>
        public event ItemHandler ItemDeepCloned;

        #region Singleton Implementation

        private EventSink()
        {
            NCommetContainer.Instance.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "DaoEventsSink")
                    this.DaoEventsSink = NCommetContainer.Instance.DaoEventsSink;
            };
        }

        /// <summary>
        /// Returns the sole instance of EventSink.
        /// </summary>
        public static EventSink Instance { get { return Nested.instance; } }

        class Nested
        {
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static Nested() { }
            internal static readonly EventSink instance = new EventSink();
        }

        #endregion

        private IDaoEventsSink daoEventsSink;

        /// <summary>
        /// Gets and sets the daoEventsSink. EventSink subscribes to <c>DaoEventsSink</c> events
        /// and propagates them to its own subscribers.
        /// </summary>
        public IDaoEventsSink DaoEventsSink
        {
            get { return daoEventsSink; }
            set
            {
                if (daoEventsSink != null)
                    daoEventsSink.RemoveDaoEventsListener(this);
                daoEventsSink = value;
                if (daoEventsSink != null)
                    daoEventsSink.AddDaoEventsListener(this);
            }
        }

        internal void FireItemShallowCloned(ItemEventArgs e)
        {
            if (ItemShallowCloned != null)
                ItemShallowCloned(this, e);
        }

        internal void FireItemDeepCloned(ItemEventArgs e)
        {
            if (ItemDeepCloned != null)
                ItemDeepCloned(this, e);
        }

        internal void FireItemCreated(ItemEventArgs e)
        {
            if (ItemCreated != null)
                ItemCreated(this, e);
        }

        internal void FireItemAdded(ItemEventArgs e)
        {
            if (ItemAdded != null)
                ItemAdded(this, e);
        }

        internal void FireItemSaving(ItemEventArgs e)
        {
            if (ItemSaving != null)
                ItemSaving(this, e);
        }

        internal void FireItemSaved(ItemEventArgs e)
        {
            if (ItemSaved != null)
                ItemSaved(this, e);
        }

        internal void FireItemChangedParent(ItemChangedParentArgs e)
        {
            if (ItemChangedParent != null)
                ItemChangedParent(this, e);
        }

        internal void FireItemDeleting(ItemCancellableEventArgs e)
        {
            if (ItemDeleting != null)
            {
                Delegate[] delegates = ItemDeleting.GetInvocationList();
                foreach (Delegate d in delegates)
                {
                    (d as ItemCancellableHandler)(this, e);
                    if (e.Cancel)
                        return; // break loop
                }
            }
        }

        internal void FireItemDeleted(ItemEventArgs e)
        {
            if (ItemDeleted != null)
                ItemDeleted(this, e);
        }

        internal void FireChildrenCleared(ItemEventArgs e)
        {
            if (ChildrenCleared != null)
                ChildrenCleared(this, e);
        }

        internal void FireChildrenReordered(ItemEventArgs e)
        {
            if (ChildrenReordered != null)
                ChildrenReordered(this, e);
        }

        internal void FireItemChangingParent(ItemChangingParentEventArgs e)
        {
            if (ItemChangingParent != null)
            {
                Delegate[] delegates = ItemChangingParent.GetInvocationList();
                foreach (Delegate d in delegates)
                {
                    (d as ItemChangingParentHandler)(this, e);
                    if (e.Cancel)
                        return; // break loop
                }
            }
        }

        #region IDaoEventsListener Members

        void IDaoEventsListener.ItemSaved(Item item)
        {
            if (ItemSaved != null)
                ItemSaved(this, new ItemEventArgs(item));
        }

        void IDaoEventsListener.ItemDeleted(Item item)
        {
            if (ItemDeleted != null)
                ItemDeleted(this, new ItemEventArgs(item));
        }

        void IDaoEventsListener.ItemSaving(Item item)
        {
            if (ItemSaving != null)
                ItemSaving(this, new ItemEventArgs(item));
        }

        #endregion
    }
}
